#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <WiFiManager.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <FS.h>  


// 考勤结果存储结构（保持不变）
struct AttendanceResult {
  String title;           // 标题行
  String total;           // 应到人数
  String present;         // 实到人数（原始）
  String absent;          // 未到人数（原始）
  String presentNames;    // 实到名单（原始）
  String absentNames;     // 未到名单（原始）
  String receiveTime;     // 接收时间
  int period;             // 课程编号（0-3，-1表示未匹配）
  bool isPreClass;        // 是否是课前签到
  String signInNames;     // 签到名单（课前实到）
  String lateNames;       // 迟到名单（课中补签）
  String remainAbsentNames; // 剩余未到名单
  int signInCount;        // 签到人数
  int lateCount;          // 迟到人数
  int remainAbsentCount;  // 剩余未到人数
  bool isValid = false;   // 是否有效
  bool isClassEnd = false;
};


// 历史考勤配置（保持不变）
#define MAX_HISTORY_COUNT 6
AttendanceResult attendanceHistory[MAX_HISTORY_COUNT];
int historyCount = 0;
const char* historyFilePath = "/attendance_history.json";

AttendanceResult currentAttendance;
AttendanceResult preClassAttendances[5];

// 登录状态定义（保持不变）
enum UserRole {
  ROLE_NONE,
  ROLE_ADMIN,
  ROLE_USER
};
UserRole currentRole = ROLE_NONE;

// 时间管理变量（保持不变）
bool useManualTime = false;
unsigned long manualEpochTime = 0;
unsigned long lastManualTimeUpdate = 0;

// NTP客户端配置（保持不变）
WiFiUDP ntpUDP;
NTPClient ntpClient(ntpUDP, "pool.ntp.org", 28800, 60000); 

// 心知天气API配置（保持不变）
const char* weatherHost = "api.seniverse.com";
String reqUserKey = "SvBYLQk80Zh0w_Zya";  
String reqLocation = "Zhengzhou";           
String reqUnit = "c";                       

// 当前工作模式（保持不变）
int currentMode = 0;

// Web服务器（保持不变）
ESP8266WebServer server(80);

// 课程时间定义（保持不变）
const int classPeriods[5][3] = {
  {495, 510, 605},   // 第一节课：8:15-8:30(课前)，8:30-10:05(课中)
  {610, 625, 720},   // 第二节课：10:10-10:25(课前)，10:25-12:00(课中)
  {825, 840, 935},   // 第三节课：13:45-14:00(课前)，14:00-15:35(课中)
  {940, 955, 1050},  // 第四节课：15:40-15:55(课前)，15:55-17:30(课中)
  {1095, 1110, 1205} // 第五节课：18:15-18:30(课前)，18:30-20:05(课中)
};


bool classEndProcessed[5] = {false, false, false, false, false}; 
enum AttendanceSection {
  NONE, TOTAL, PRESENT, ABSENT, PRESENT_NAMES, ABSENT_NAMES
};

// 考勤数据缓冲（保持不变）
String attendanceBuffer = "";  
bool isCollectingAttendance = false; 

// 天气数据结构（保持不变）
struct WeatherData {
  String city; String timezone; String date;
  String text_day; String code_day; String high; String low;
  String wind_dir; String wind_speed; String humidity; String last_update;
  bool isUpdated = false;
} weatherData;

// 更新参数（保持不变）
unsigned long lastWeatherUpdate = 0;
const unsigned long WEATHER_UPDATE_INTERVAL = 60000; 
unsigned long lastNtpUpdate = 0;
const unsigned long NTP_UPDATE_INTERVAL = 60000; 
unsigned long lastSerialTimeSend = 0;
const unsigned long SERIAL_TIME_INTERVAL = 30000; 

WiFiManager wifiManager;
const bool DEBUG = false;

// 处理课程结束逻辑（优化：保留迟到信息）
void processClassEnd(int period) {
  if (classEndProcessed[period]) return;
  if (!preClassAttendances[period].isValid) return;

  AttendanceResult endResult = preClassAttendances[period];
  // 优化：同步课中补签的迟到数据
  if (currentAttendance.isValid && currentAttendance.period == period && !currentAttendance.isPreClass) {
    endResult.lateNames = currentAttendance.lateNames;       // 保留迟到名单
    endResult.lateCount = currentAttendance.lateCount;       // 保留迟到人数
    endResult.remainAbsentNames = currentAttendance.remainAbsentNames;
    endResult.remainAbsentCount = currentAttendance.remainAbsentCount;
  }

  endResult.isClassEnd = true;
  endResult.title = "课程结束 - 最终缺勤结果";
  endResult.receiveTime = getDisplayTime();
  endResult.isValid = true;

  currentAttendance = endResult;
  addToHistory(endResult);
  classEndProcessed[period] = true;
  // Serial.printf("第%d节课结束，迟到%d人，最终缺勤%d人\n", 
  //              period+1, endResult.lateCount, endResult.remainAbsentCount);
}

// SPIFFS文件操作与历史记录管理（保持不变）
bool initFS() {
  if (!SPIFFS.begin()) {
    Serial.println("SPIFFS初始化失败！");
    return false;
  }
  // Serial.println("SPIFFS初始化成功");
  return true;
}

String serializeAttendance(AttendanceResult &att) {
  const size_t capacity = JSON_OBJECT_SIZE(16) + 1000;
  DynamicJsonDocument doc(capacity);
  
  doc["title"] = att.title;
  doc["total"] = att.total;
  doc["present"] = att.present;
  doc["absent"] = att.absent;
  doc["presentNames"] = att.presentNames;
  doc["absentNames"] = att.absentNames;
  doc["receiveTime"] = att.receiveTime;
  doc["period"] = att.period;
  doc["isPreClass"] = att.isPreClass;
  doc["signInNames"] = att.signInNames;
  doc["lateNames"] = att.lateNames;
  doc["remainAbsentNames"] = att.remainAbsentNames;
  doc["signInCount"] = att.signInCount;
  doc["lateCount"] = att.lateCount;
  doc["remainAbsentCount"] = att.remainAbsentCount;
  doc["isValid"] = att.isValid;
  doc["isClassEnd"] = att.isClassEnd;

  String json;
  serializeJson(doc, json);
  return json;
}

AttendanceResult deserializeAttendance(String json) {
  AttendanceResult att;
  const size_t capacity = JSON_OBJECT_SIZE(16) + 1000;
  DynamicJsonDocument doc(capacity);
  
  DeserializationError error = deserializeJson(doc, json);
  if (error) {
    Serial.print("反序列化失败: ");
    Serial.println(error.c_str());
    att.isValid = false;
    return att;
  }
  
  att.title = doc["title"].as<String>();
  att.total = doc["total"].as<String>();
  att.present = doc["present"].as<String>();
  att.absent = doc["absent"].as<String>();
  att.presentNames = doc["presentNames"].as<String>();
  att.absentNames = doc["absentNames"].as<String>();
  att.receiveTime = doc["receiveTime"].as<String>();
  att.period = doc["period"].as<int>();
  att.isPreClass = doc["isPreClass"].as<bool>();
  att.signInNames = doc["signInNames"].as<String>();
  att.lateNames = doc["lateNames"].as<String>();
  att.remainAbsentNames = doc["remainAbsentNames"].as<String>();
  att.signInCount = doc["signInCount"].as<int>();
  att.lateCount = doc["lateCount"].as<int>();
  att.remainAbsentCount = doc["remainAbsentCount"].as<int>();
  att.isValid = doc["isValid"].as<bool>();
  att.isClassEnd = doc["isClassEnd"].as<bool>();

  return att;
}

void handleClearHistory() {
  if (currentRole != ROLE_ADMIN) {
    server.send(403, "text/plain", "无权限（需管理员登录）");
    return;
  }

  if (SPIFFS.exists(historyFilePath)) {
    SPIFFS.remove(historyFilePath);
  }
  historyCount = 0;
  for (int i = 0; i < MAX_HISTORY_COUNT; i++) {
    attendanceHistory[i].isValid = false;
  }
  saveHistory();
  server.send(200, "text/plain", "历史记录已成功清空！");
}

void loadHistory() {
  if (!SPIFFS.exists(historyFilePath)) return;
  
  File file = SPIFFS.open(historyFilePath, "r");
  DynamicJsonDocument doc(1024 * 8);
  deserializeJson(doc, file);
  file.close();
  
  JsonArray array = doc.as<JsonArray>();
  historyCount = min(array.size(), (size_t)MAX_HISTORY_COUNT);
  
  for (int i = 0; i < historyCount; i++) {
    String recJson;
    serializeJson(array[i], recJson);
    attendanceHistory[i] = deserializeAttendance(recJson);
  }
}

void saveHistory() {
  // Serial.println("[DEBUG] 保存历史记录到文件...");
  
  const size_t capacity = JSON_ARRAY_SIZE(MAX_HISTORY_COUNT) + MAX_HISTORY_COUNT * (JSON_OBJECT_SIZE(15) + 1000);
  DynamicJsonDocument doc(capacity);
  JsonArray array = doc.to<JsonArray>();
  
  for (int i = 0; i < historyCount; i++) {
    String recJson = serializeAttendance(attendanceHistory[i]);
    DynamicJsonDocument tempDoc(1024);
    deserializeJson(tempDoc, recJson);
    JsonObject obj = array.createNestedObject();
    obj.set(tempDoc.as<JsonObject>());
  }
  
  File file = SPIFFS.open(historyFilePath, "w");
  if (!file) {
    Serial.println("[ERROR] 无法打开文件");
    return;
  }
  serializeJson(doc, file);
  file.close();
}

void addToHistory(AttendanceResult &newAtt) {
  if (historyCount >= MAX_HISTORY_COUNT) {
    for (int i = 0; i < MAX_HISTORY_COUNT - 1; i++) {
      attendanceHistory[i] = attendanceHistory[i + 1];
    }
    historyCount--;
  }
  
  attendanceHistory[historyCount] = newAtt;
  historyCount++;
  saveHistory();
}

String getHistoryHTML() {
  if (historyCount == 0) {
    return "<p>暂无历史考勤记录</p>";
  }
  
  String html = "<h3>历史考勤记录（最近" + String(historyCount) + "条）</h3>";
  
  for (int i = historyCount - 1; i >= 0; i--) {
    AttendanceResult &rec = attendanceHistory[i];
    if (!rec.isValid) continue;
    
    String type = rec.isClassEnd ? "课程结束" : (rec.isPreClass ? "课前考勤" : "课中补签");
    String period = "第" + String(rec.period + 1) + "节课";
    
    html += "<div class='history-item'>"
            "<p><strong>" + type + " - " + period + "</strong> （" + rec.receiveTime + "）</p>";
    
    if (rec.isClassEnd) {
      html += "<p>应到：" + rec.total + " | 迟到：" + String(rec.lateCount) + "人 | 最终缺勤：" + String(rec.remainAbsentCount) + "人</p>"
              "<p>迟到名单：" + rec.lateNames + "</p>"  // 显示迟到名单
              "<p>缺勤名单：" + rec.remainAbsentNames + "</p>";
    } else {
      html += "<p>应到：" + rec.total + " | 签到：" + String(rec.signInCount) + "人 | 迟到：" + String(rec.lateCount) + "人 | 未到：" + String(rec.remainAbsentCount) + "人</p>"
              "<p>签到名单：" + rec.signInNames + "</p>";
      if (!rec.isPreClass) {
        html += "<p>迟到名单：" + rec.lateNames + "</p>";
      }
      html += "<p>未到名单：" + rec.remainAbsentNames + "</p>";
    }
    html += "</div>";
  }
  
  return html;
}


// 原有函数（优化：CSV导出格式、考勤解析兼容带"人"字格式）
void handleExportAttendance() {
  if (currentRole != ROLE_ADMIN) {
    server.send(403, "text/plain", "无权限导出");
    return;
  }

  // 优化：按需求调整CSV字段顺序
  String csvHeader = "标题,应到人数,签到人数,迟到人数,缺勤人数,课程时间编号,接收时间,签到名单,迟到名单,未到|缺勤名单\r\n";
  String csvBody;

  for (int i = 0; i < historyCount; ++i) {
    AttendanceResult &rec = attendanceHistory[i];
    if (!rec.isValid) continue;
    
    csvBody += "\"" + rec.title + "\"," 
             + rec.total + "," 
             + String(rec.signInCount) + ","    // 签到人数
             + String(rec.lateCount) + ","      // 迟到人数
             + String(rec.remainAbsentCount) + ","  // 缺勤人数
             + String(rec.period) + "," 
             + rec.receiveTime + "," 
             + rec.signInNames + "," 
             + rec.lateNames + "," 
             + rec.remainAbsentNames + "\r\n";
  }

  String csvData = csvHeader + csvBody;
  server.sendHeader("Content-Disposition", "attachment; filename=\"attendance_history.csv\"");
  server.sendHeader("Content-Type", "text/csv");
  server.sendHeader("Content-Length", String(csvData.length()));
  server.send(200, "text/csv", csvData);
}

// 时间相关函数（保持不变）
time_t getCurrentEpochTime() {
  return useManualTime ? manualEpochTime : ntpClient.getEpochTime();
}

String getFormattedTime() {
  time_t now = getCurrentEpochTime();
  struct tm *timeinfo = localtime(&now);  
  char buffer[13];
  sprintf(buffer, "%02d%02d%02d%02d%02d%02d", 
          timeinfo->tm_year % 100, timeinfo->tm_mon + 1, timeinfo->tm_mday,
          timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
  return String(buffer);
}

String getDisplayTime() {
  time_t now = getCurrentEpochTime();
  struct tm *timeinfo = localtime(&now);  
  char buffer[20];
  sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d",
          timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday,
          timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
  return String(buffer);
}

void getHourMinute(int &hour, int &minute) {
  time_t now = getCurrentEpochTime();
  struct tm *timeinfo = localtime(&now);  
  hour = timeinfo->tm_hour;
  minute = timeinfo->tm_min;
}

int getClassPeriod(int hour, int minute, bool &isPre) {
  int currentMinute = hour * 60 + minute;
  for (int i = 0; i < 5; i++) {
    if (currentMinute >= classPeriods[i][0] && currentMinute < classPeriods[i][1]) {
      isPre = true;
      return i;
    }
    if (currentMinute >= classPeriods[i][1] && currentMinute < classPeriods[i][2]) {
      isPre = false;
      return i;
    }
  }
  isPre = false;
  return -1;
}

// 字符串分割函数（保持不变）
std::vector<String> splitString(String str, char delimiter) {
  std::vector<String> result;
  int start = 0;
  int end = str.indexOf(delimiter);
  while (end != -1) {
    String sub = str.substring(start, end);
    sub.trim();
    if (!sub.isEmpty()) {
      result.push_back(sub);
    }
    start = end + 1;
    end = str.indexOf(delimiter, start);
  }
  String sub = str.substring(start);
  sub.trim();
  if (!sub.isEmpty()) {
    result.push_back(sub);
  }
  return result;
}

// 检查字符串是否在数组中（保持不变）
bool isInList(String item, std::vector<String> list) {
  for (size_t i = 0; i < list.size(); i++) {  // 传统for循环兼容旧环境
    if (list[i].equalsIgnoreCase(item)) {
      return true;
    }
  }
  return false;
}

// 解析缓冲的考勤数据（优化：兼容带"人"字的格式）
AttendanceResult parseAttendanceBuffer(String buffer) {
  AttendanceResult temp;
  AttendanceSection section = NONE;             

  int start = 0;
  int end = buffer.indexOf('\n');

  while (end != -1) {
    String line = buffer.substring(start, end);
    line.trim();
    
    start = end + 1;
    end = buffer.indexOf('\n', start);

    if (line.isEmpty()) continue;

    if (line.startsWith("考勤结束 - 结果:")) {
      temp.title = line;
    } else if (line.startsWith("应到:")) {
      int colon = line.indexOf(':');
      if (colon != -1) {
        String tempStr = line.substring(colon + 1);
        tempStr.trim();
        tempStr.replace("人", "");  // 移除"人"字
        temp.total = tempStr;
        temp.signInCount = temp.total.toInt();
      }
    } else if (line.startsWith("实到:")) {
      int colon = line.indexOf(':');
      if (colon != -1) {
        String tempStr = line.substring(colon + 1);
        tempStr.trim();
        tempStr.replace("人", "");  // 移除"人"字
        temp.present = tempStr;
      }
    } else if (line.startsWith("未到:")) {
      int colon = line.indexOf(':');
      if (colon != -1) {
        String tempStr = line.substring(colon + 1);
        tempStr.trim();
        tempStr.replace("人", "");  // 移除"人"字
        temp.absent = tempStr;
      }
    } else if (line.startsWith("实到名单:")) {
      section = PRESENT_NAMES;
      temp.presentNames = "";
    } else if (line.startsWith("未到名单:")) {
      section = ABSENT_NAMES;
      temp.absentNames = "";
    } else {
      if (section == PRESENT_NAMES) {
        temp.presentNames += line;
      } else if (section == ABSENT_NAMES) {
        temp.absentNames += line;
      }
    }
  }

  if (temp.title.isEmpty() || temp.total.isEmpty() || temp.present.isEmpty() || 
      temp.absent.isEmpty() || temp.presentNames.isEmpty() || temp.absentNames.isEmpty()) {
    temp.isValid = false;
    return temp;
  }

  temp.isValid = true;
  return temp;
}

// 处理补签逻辑（优化：传统for循环兼容旧环境）
void processLateSignIn(AttendanceResult &current, AttendanceResult &pre) {
  std::vector<String> preAbsentList = splitString(pre.absentNames, ',');
  std::vector<String> currentPresentList = splitString(current.presentNames, ',');
  
  current.signInNames = pre.presentNames;
  current.signInCount = splitString(pre.presentNames, ',').size();

  current.lateNames = "";
  // 优化：传统for循环替代范围for
  for (size_t i = 0; i < currentPresentList.size(); i++) {
    String name = currentPresentList[i];
    if (isInList(name, preAbsentList)) {
      current.lateNames += (current.lateNames.isEmpty() ? "" : ", ") + name;
    }
  }
  current.lateCount = splitString(current.lateNames, ',').size();

  current.remainAbsentNames = "";
  std::vector<String> lateList = splitString(current.lateNames, ',');
  // 优化：传统for循环替代范围for
  for (size_t i = 0; i < preAbsentList.size(); i++) {
    String name = preAbsentList[i];
    if (!isInList(name, lateList)) {
      current.remainAbsentNames += (current.remainAbsentNames.isEmpty() ? "" : ", ") + name;
    }
  }
  current.remainAbsentCount = splitString(current.remainAbsentNames, ',').size();
}

// 记录串口数据（保持不变）
void logSerialData(String data) {
  data.trim();
  
  if (data.startsWith("考勤结束 - 结果:")) {
    isCollectingAttendance = true;
    attendanceBuffer = data + "\n";
    currentAttendance = AttendanceResult();
    currentAttendance.receiveTime = getDisplayTime();
    currentAttendance.title = data;
  } else if (isCollectingAttendance) {
    attendanceBuffer += data + "\n";
    
    AttendanceResult parsed = parseAttendanceBuffer(attendanceBuffer);
    if (!parsed.isValid) return;

    int hour, minute;
    getHourMinute(hour, minute);
    bool isPre;
    int period = getClassPeriod(hour, minute, isPre);

    // 新增：非课表时间判断与提示
    if (period == -1) {
      // 创建不在有效时间的提示结果
      currentAttendance.isValid = true;
      currentAttendance.title = "当前不在有效考勤时间";
      currentAttendance.receiveTime = getDisplayTime();
      currentAttendance.total = "";
      currentAttendance.present = "";
      currentAttendance.absent = "";
      currentAttendance.presentNames = "";
      currentAttendance.absentNames = "";
      currentAttendance.period = -1;
      currentAttendance.isPreClass = false;
      
      isCollectingAttendance = false;
      attendanceBuffer = "";
      return;
    }

    parsed.period = period;
    parsed.isPreClass = isPre;
    parsed.receiveTime = currentAttendance.receiveTime;
    parsed.title = currentAttendance.title;

    if (isPre) {
      parsed.signInNames = parsed.presentNames;
      parsed.signInCount = splitString(parsed.presentNames, ',').size();
      parsed.remainAbsentNames = parsed.absentNames;
      parsed.remainAbsentCount = splitString(parsed.absentNames, ',').size();
      parsed.lateNames = "";
      parsed.lateCount = 0;
      preClassAttendances[period] = parsed;
      currentAttendance = parsed;
    } else {
      if (!preClassAttendances[period].isValid) {
        isCollectingAttendance = false;
        attendanceBuffer = "";
        return;
      }
      parsed.isPreClass = false;
      processLateSignIn(parsed, preClassAttendances[period]);
      currentAttendance = parsed;
    }

    currentAttendance.isValid = true;
    isCollectingAttendance = false;
    attendanceBuffer = "";
    addToHistory(currentAttendance);
  }
}

// Web页面相关函数（保持不变）
String getLoginHTML() {
  return "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>登录</title><style>"
         "body{font-family:Arial;text-align:center;margin:50px;}"
         ".login-box{max-width:300px;margin:auto;padding:20px;border:1px solid #ccc;border-radius:10px;}"
         "input{width:100%;padding:10px;margin:10px 0;border:1px solid #ddd;border-radius:5px;}"
         "button{background-color:#4CAF50;color:white;padding:10px 20px;border:none;border-radius:5px;cursor:pointer;width:100%;}"
         "button:hover{background-color:#45a049;}"
         ".error{color:red;margin:10px 0;}"
         "</style></head><body>"
         "<div class=\"login-box\"><h2>请登录</h2>"
         "<form action=\"/login\" method=\"post\">"
         "<input type=\"text\" name=\"username\" placeholder=\"用户名\" required><br>"
         "<input type=\"password\" name=\"password\" placeholder=\"密码\" required><br>"
         "<button type=\"submit\">登录</button>"
         "</form>"
         "<div class=\"error\" id=\"errorMsg\"></div>"
         "</div></body></html>";
}

String getAdminHTML() {
  time_t now = getCurrentEpochTime();
  struct tm *timeinfo = localtime(&now);
  
  String html = getBaseHTML();
  
  html += "<div class=\"time-setting info-box\"><h3>修改系统时间</h3>"
          "<form action=\"/setmanualtime\" method=\"get\">"
          "<input type=\"number\" name=\"year\" placeholder=\"年\" min=\"2000\" max=\"2100\" value=\"" + String(timeinfo->tm_year + 1900) + "\"> "
          "<input type=\"number\" name=\"month\" placeholder=\"月\" min=\"1\" max=\"12\" value=\"" + String(timeinfo->tm_mon + 1) + "\"> "
          "<input type=\"number\" name=\"day\" placeholder=\"日\" min=\"1\" max=\"31\" value=\"" + String(timeinfo->tm_mday) + "\"> "
          "<input type=\"number\" name=\"hour\" placeholder=\"时\" min=\"0\" max=\"23\" value=\"" + String(timeinfo->tm_hour) + "\"> "
          "<input type=\"number\" name=\"minute\" placeholder=\"分\" min=\"0\" max=\"59\" value=\"" + String(timeinfo->tm_min) + "\"> "
          "<input type=\"number\" name=\"second\" placeholder=\"秒\" min=\"0\" max=\"59\" value=\"" + String(timeinfo->tm_sec) + "\"> "
          "<button type=\"submit\" class=\"button\">设置时间</button>"
          "</form>"
          "<button class=\"button\" onclick=\"useNtpTime()\">切换到NTP时间</button>"
          "</div>";
  html += "<div class=\"info-box\"><h3>历史记录管理</h3>"
          "<button class=\"button\" onclick=\"clearHistory()\">清空历史记录</button>"
          "<button class=\"button\" onclick=\"exportAttendance()\">导出历史记录（CSV）</button>" 
          "</div>";        
  html += getCommonHTML();
  html += "</body></html>";
  return html;
}

String getUserHTML() {
  String html = getBaseHTML();
  html += getCommonHTML();
  html += "</body></html>";
  return html;
}

String getBaseHTML() {
  return "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>控制中心</title><style>"
         "body{font-family:Arial;text-align:center;margin:20px;}"
         "h1{color:#444;}"
         ".button{background-color:#4CAF50;border:none;color:white;padding:15px 32px;text-align:center;text-decoration:none;display:inline-block;font-size:16px;margin:10px;cursor:pointer;border-radius:5px;box-shadow:0 4px 8px rgba(0,0,0,0.2);transition:all 0.3s;}"
         ".button:active{box-shadow:0 2px 4px rgba(0,0,0,0.4);transform:translateY(2px);}"
         ".button-mode-auto{background-color:#4CAF50;}"
         ".button-mode-attendance{background-color:#FF9800;}"
         ".button-mode-security{background-color:#f44336;}"
         ".info-box,.weather-box,.time-box,.attendance-box{background-color:#f0f0f0;padding:15px;margin:20px;border-radius:5px;}"
         ".history-box{background-color:#f9f9f9;padding:15px;margin:20px;border-radius:5px;border-left:4px solid #2196F3;}"
         ".history-item{border:1px solid #eee;padding:10px;margin:10px 0;border-radius:5px;text-align:left;}"
         ".weather-box{background-color:#e3f2fd;}"
         ".weather-code{color:#2196F3;font-weight:bold;}"
         ".city-selector,.phone-form{margin:15px;}"
         ".city-input,.phone-input{padding:10px;margin-right:5px;border:1px solid #ddd;border-radius:4px;}"
         ".city-button,.phone-button{padding:10px;background-color:#2196F3;color:white;border:none;border-radius:4px;cursor:pointer;}"
         ".time-box{background-color:#fff3e0;border-left:4px solid #FF9800;}"
         ".attendance-box{background-color:#e8f5e9;border-left:4px solid #4CAF50;overflow-y:auto;text-align:left;}"
         ".serial-time{color:#666;font-size:12px;}"
         ".attendance-title{font-weight:bold;margin-bottom:10px;}"
         ".attendance-line{margin:5px 0;}"
         ".time-setting input{width:60px;margin:0 5px;}"
         "</style></head><body><h1>控制中心</h1>";
}

String getCommonHTML() {
  String html = "";
  
  html += "<div class=\"time-box\" id=\"currentTime\"><h3>当前时间</h3><p>" + getDisplayTime() + "</p>"
          "<p>" + (useManualTime ? "（手动设置时间）" : "（NTP同步时间）") + "</p></div>"
          "<script>setInterval(()=>{fetch('/gettime').then(r=>r.text()).then(d=>document.getElementById('currentTime').querySelector('p').innerText=d);},1000);</script>";
  
  html += "<div class=\"info-box\"><h3>网络状态</h3><p>当前连接: " + WiFi.SSID() + "</p><p>IP地址: " + WiFi.localIP().toString() + "</p></div>";
  
  html += "<div class=\"info-box\"><h3>工作模式与功能</h3>"
          "<button class=\"button button-mode-auto\" onclick=\"setMode(0)\">自动模式</button>"
          "<button class=\"button button-mode-attendance\" onclick=\"setMode(1)\">考勤签到</button>"
          "<button class=\"button button-mode-security\" onclick=\"setMode(2)\">安全守护</button>"
          "<button class=\"button\" onclick=\"sendAction('faceres')\">解析人脸</button>"
          "</div>";
  
  html += "<div class=\"phone-form info-box\"><h3>手机号设置</h3>"
          "<input type=\"text\" class=\"phone-input\" id=\"phoneNumber\" placeholder=\"输入11位手机号\" maxlength=\"11\">"
          "<button onclick=\"sendPhoneNumber()\" class=\"phone-button\">确认</button>"
          "</div>";
  
  html += String("<div class=\"attendance-box\" id=\"attendanceResult\"><h3>") + 
          (currentAttendance.isValid ? (currentAttendance.isPreClass ? "考勤结果" : "补签结果") : "考勤结果") + 
          "</h3>" + getAttendanceHTML() + "</div>";
  
  html += "<div class='history-box'>" + getHistoryHTML() + "</div>";
  
  html += "<div class=\"city-selector\"><form action=\"/setcity\" method=\"get\">"
          "<input type=\"text\" class=\"city-input\" name=\"city\" placeholder=\"输入城市名称\" value=\"" + reqLocation + "\">"
          "<button type=\"submit\" class=\"city-button\">更改城市</button>"
          "</form></div>"
          "<div class=\"weather-box\"><h3>天气信息</h3>" + getWeatherHTML() + "</div>"
          "<script>"
          "function sendAction(a){fetch('/'+a).then(r=>r.text());}"
          "function setMode(m){fetch('/setmode?mode='+m);}"
          "function sendPhoneNumber(){let p=document.getElementById('phoneNumber').value;fetch('/setphone?phone='+p).then(r=>r.text()).then(a=>alert(a));}"
          "function useNtpTime(){fetch('/useNtpTime');location.reload();}"
          "setInterval(()=>{"
            "fetch('/getattendance')"
            ".then(r=>r.text())"
            ".then(d=>{"
              "document.getElementById('attendanceResult').innerHTML='<h3>考勤结果</h3>'+d;"
              "fetch('/getHistoryHTML')"
              ".then(r=>r.text())"
              ".then(html=>{"
                "const historyBox = document.querySelector('.history-box');"
                "if(historyBox) historyBox.innerHTML = html;"
              "});"
            "});"
          "},1000);"
          "function clearHistory() {"
            "if(confirm('确定要清空所有历史记录吗？')) {"
              "fetch('/clearHistory')"
              ".then(r=>r.text())"
              ".then(msg=>{"
                "alert(msg);"
                "location.reload();"
              "});"
            "}"
          "}"
          "function exportAttendance() {"
            "window.open('/exportAttendance', '_blank');"
          "}"
          "setInterval(()=>{"
            "fetch('/getattendance')"
            ".then(r=>r.text())"
            ".then(d=>{"
              "document.getElementById('attendanceResult').innerHTML='<h3>考勤结果</h3>'+d;"
              "fetch('/getHistoryHTML')"
              ".then(r=>r.text())"
              ".then(html=>{"
                "const historyBox = document.querySelector('.history-box');"
                "if(historyBox) historyBox.innerHTML = html;"
              "});"
            "});"
          "},1000);"
          "</script>";
          
  return html;
}

String getAttendanceHTML() {
  if (!currentAttendance.isValid) {
    return "<p>等待接收考勤数据...</p>";
  }

  // 新增：特殊处理非课表时间的提示
  if (currentAttendance.period == -1) {
    return "<p class=\"serial-time\">接收时间: " + currentAttendance.receiveTime + "</p>"
           "<div class=\"attendance-title\" style=\"color:red;\">" + currentAttendance.title + "</div>"
           "<div class=\"attendance-line\">当前时间不在任何课程时段内</div>"
           "<div class=\"attendance-line\" style=\"font-size:0.9em;\">(请在课程开始前15分钟至课程结束后5分钟内进行考勤)</div>";
  }

  String html = "<p class=\"serial-time\">接收时间: " + currentAttendance.receiveTime + "</p>"
                "<div class=\"attendance-title\">" + currentAttendance.title + "</div>"
                "<div class=\"attendance-line\">应到: " + currentAttendance.total + "</div>";
  if (currentAttendance.isClassEnd) {
    html += "<div class=\"attendance-line\">迟到: " + String(currentAttendance.lateCount) + "人</div>"  // 显示迟到人数
            "<div class=\"attendance-line\">最终缺勤: " + String(currentAttendance.remainAbsentCount) + "人</div>"
            "<div class=\"attendance-line\">迟到名单: " + currentAttendance.lateNames + "</div>"  // 显示迟到名单
            "<div class=\"attendance-line\">缺勤名单: " + currentAttendance.remainAbsentNames + "</div>";
  }
  else if (currentAttendance.isPreClass) {
    html += "<div class=\"attendance-line\">签到: " + String(currentAttendance.signInCount) + "人</div>"
            "<div class=\"attendance-line\">未到: " + String(currentAttendance.remainAbsentCount) + "人</div>"
            "<div class=\"attendance-line\">签到名单: " + currentAttendance.signInNames + "</div>"
            "<div class=\"attendance-line\">未到名单: " + currentAttendance.remainAbsentNames + "</div>";
  } else {
    html += "<div class=\"attendance-line\">签到: " + String(currentAttendance.signInCount) + "人</div>"
            "<div class=\"attendance-line\">迟到: " + String(currentAttendance.lateCount) + "人</div>"
            "<div class=\"attendance-line\">未到: " + String(currentAttendance.remainAbsentCount) + "人</div>"
            "<div class=\"attendance-line\">签到名单: " + currentAttendance.signInNames + "</div>"
            "<div class=\"attendance-line\">迟到名单: " + currentAttendance.lateNames + "</div>"
            "<div class=\"attendance-line\">未到名单: " + currentAttendance.remainAbsentNames + "</div>";
  }

  return html;
}

String getWeatherHTML() {
  if (weatherData.isUpdated) {
    return "<p>" + weatherData.city + " - " + weatherData.date + "</p>"
           "<p><span class=\"weather-code\">" + weatherData.text_day + "</span> | 温度: " + weatherData.low + "~" + weatherData.high + "°C</p>"
           "<p>风速: " + weatherData.wind_speed + "km/h | 湿度: " + weatherData.humidity + "%</p>"
           "<p style=\"font-size:12px;color:#666;\">更新时间: " + weatherData.last_update + "</p>";
  } else {
    return "<p>加载天气信息中...</p>";
  }
}

// 天气数据获取（保持不变）
bool fetchWeatherData() {
  WiFiClient client;
  if (!client.connect(weatherHost, 80)) {
    if (DEBUG) Serial.println("无法连接天气服务器");
    return false;
  }

  String reqRes = "/v3/weather/daily.json?key=" + reqUserKey +
                  "&location=" + reqLocation + "&language=en&unit=" + reqUnit + "&start=0&days=1";
  String httpRequest = "GET " + reqRes + " HTTP/1.1\r\n" +
                       "Host: " + weatherHost + "\r\n" +
                       "Connection: close\r\n\r\n";
  client.print(httpRequest);

  if (!client.find("\r\n\r\n")) {
    if (DEBUG) Serial.println("响应头解析失败");
    client.stop();
    return false;
  }

  const size_t capacity = JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(1) + 
                          JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(6) + 
                          JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(14) + 
                          JSON_OBJECT_SIZE(7) + 500; 
  DynamicJsonDocument doc(capacity);
  DeserializationError error = deserializeJson(doc, client);
  client.stop();

  if (error) {
    if (DEBUG) Serial.print("天气解析错误: ");
    if (DEBUG) Serial.println(error.c_str());
    return false;
  }

  if (!doc["results"].size()) {
    if (DEBUG) Serial.println("无天气数据");
    return false;
  }

  JsonObject location = doc["results"][0]["location"];
  weatherData.city = location["name"].as<String>();
  weatherData.timezone = location["timezone"].as<String>();
  
  JsonObject today = doc["results"][0]["daily"][0];
  weatherData.date = today["date"].as<String>();
  weatherData.text_day = today["text_day"].as<String>();
  weatherData.code_day = today["code_day"].as<String>();
  weatherData.high = today["high"].as<String>();
  weatherData.low = today["low"].as<String>();
  weatherData.wind_dir = today["wind_direction"].as<String>();
  weatherData.wind_speed = today["wind_speed"].as<String>();
  weatherData.humidity = today["humidity"].as<String>();
  weatherData.last_update = doc["results"][0]["last_update"].as<String>();
  weatherData.isUpdated = true;

  return true;
}

// 服务器路由处理（保持不变）
void handleRoot() {
  if (currentRole == ROLE_NONE) {
    server.send(200, "text/html; charset=UTF-8", getLoginHTML());
  } else if (currentRole == ROLE_ADMIN) {
    server.send(200, "text/html; charset=UTF-8", getAdminHTML());
  } else {
    server.send(200, "text/html; charset=UTF-8", getUserHTML());
  }
}

void handleLogin() {
  if (server.hasArg("username") && server.hasArg("password")) {
    String user = server.arg("username");
    String pass = server.arg("password");
    
    if (user == "admin" && pass == "admin") {
      currentRole = ROLE_ADMIN;
      server.sendHeader("Location", "/");
      server.send(303);
    } else if (user == "user" && pass == "12345678") {
      currentRole = ROLE_USER;
      server.sendHeader("Location", "/");
      server.send(303);
    } else {
      String html = getLoginHTML();
      html.replace("</div></body>", "<script>document.getElementById('errorMsg').innerText='用户名或密码错误'</script></div></body>");
      server.send(200, "text/html; charset=UTF-8", html);
    }
  } else {
    server.send(400, "text/plain", "参数错误");
  }
}

void handleSetManualTime() {
  if (currentRole != ROLE_ADMIN) {
    server.send(403, "text/plain", "无权限");
    return;
  }
  
  if (server.hasArg("year") && server.hasArg("month") && server.hasArg("day") &&
      server.hasArg("hour") && server.hasArg("minute") && server.hasArg("second")) {
    
    int y = server.arg("year").toInt();
    int m = server.arg("month").toInt();
    int d = server.arg("day").toInt();
    int h = server.arg("hour").toInt();
    int mi = server.arg("minute").toInt();
    int s = server.arg("second").toInt();
    
    if (m < 1 || m > 12 || d < 1 || d > 31 || h < 0 || h > 23 || mi < 0 || mi > 59 || s < 0 || s > 59) {
      server.send(200, "text/html", "<script>alert('无效的时间格式');history.back();</script>");
      return;
    }
    
    struct tm timeinfo;
    timeinfo.tm_year = y - 1900;
    timeinfo.tm_mon = m - 1;
    timeinfo.tm_mday = d;
    timeinfo.tm_hour = h;
    timeinfo.tm_min = mi;
    timeinfo.tm_sec = s;
    timeinfo.tm_isdst = -1;
    
    manualEpochTime = mktime(&timeinfo);
    useManualTime = true;
    lastManualTimeUpdate = millis();
    
    server.sendHeader("Location", "/");
    server.send(303);
  } else {
    server.send(400, "text/plain", "参数不完整");
  }
}

void handleUseNtpTime() {
  if (currentRole != ROLE_ADMIN) {
    server.send(403, "text/plain", "无权限");
    return;
  }
  useManualTime = false;
  ntpClient.update();
  server.send(200, "text/plain", "已切换到NTP时间");
}

void handleFaceRes() { Serial.println("faceres"); server.send(200, "text/plain", "人脸解析命令已发送"); }
void handleWeather() { server.send(200, "text/html; charset=UTF-8", getWeatherHTML()); }
void handleSetCity() { 
  if (server.hasArg("city")) {
    reqLocation = server.arg("city");
    fetchWeatherData();
    server.sendHeader("Location", "/");
    server.send(303);
  }
}
void handleSetMode() { 
  if (server.hasArg("mode")) {
    currentMode = server.arg("mode").toInt();
    Serial.println("conmod" + String(currentMode));
    server.send(200, "text/plain", "模式已设置");
  }
}
void handleSetPhone() { 
  if (server.hasArg("phone")) {
    String phone = server.arg("phone");
    Serial.println("ph:" + phone);
    server.send(200, "text/plain", "手机号已设置");
  }
}
void handleAttendance() { server.send(200, "text/html; charset=UTF-8", getAttendanceHTML()); }
void handleGetTime() { server.send(200, "text/plain", getDisplayTime()); }

// 配置WiFi（保持不变）
void configureWiFi() {
  wifiManager.setDebugOutput(false);
  wifiManager.autoConnect("ESP8266-Config", "12345678");
  Serial.print("IP:");
  Serial.println(WiFi.localIP());
}

// 检查课程结束时间（保持不变）
void checkClassEndTime() {
  int currentHour, currentMinute;
  getHourMinute(currentHour, currentMinute);
  int currentTotalMinute = currentHour * 60 + currentMinute;

  for (int period = 0; period < 5; period++) {
    int classEndMinute = classPeriods[period][2];
    if (currentTotalMinute >= classEndMinute && !classEndProcessed[period]) {
      processClassEnd(period);
    }
  }
}

// 初始化与主循环（保持不变）
void setup() {
  Serial.begin(9600);
  configureWiFi();
  ntpClient.begin();
  ntpClient.update();
  fetchWeatherData();
  
  if (initFS()) {
    loadHistory();
  }
  
  for (int i = 0; i < 5; i++) {
    preClassAttendances[i].isValid = false;
  }
  
  server.on("/", handleRoot);
  server.on("/login", HTTP_POST, handleLogin);
  server.on("/faceres", handleFaceRes);
  server.on("/weather", handleWeather);
  server.on("/setcity", handleSetCity);
  server.on("/setmode", handleSetMode);
  server.on("/setphone", handleSetPhone);
  server.on("/gettime", handleGetTime);
  server.on("/getattendance", handleAttendance);
  server.on("/setmanualtime", handleSetManualTime);
  server.on("/useNtpTime", handleUseNtpTime);
  server.on("/getHistoryHTML", []() {
    server.send(200, "text/html; charset=UTF-8", getHistoryHTML());
  });
  server.on("/clearHistory", handleClearHistory);
  server.on("/exportAttendance", handleExportAttendance);
  server.begin();
  // Serial.println("服务器启动");
}

void loop() {
  server.handleClient();
  
  if (useManualTime && millis() - lastManualTimeUpdate >= 1000) {
    manualEpochTime++;
    lastManualTimeUpdate = millis();
  }
  
  while (Serial.available() > 0) {
    String line = Serial.readStringUntil('\n');
    if (line.endsWith("\r")) line = line.substring(0, line.length() - 1);

    // 检测重启指令
    if (line.equalsIgnoreCase("restart")) {
      delay(3000);  // 确保串口数据发送完成
      ESP.restart();  // 执行软重启
    }
    logSerialData(line);
  }
  
  if (!useManualTime && millis() - lastNtpUpdate >= NTP_UPDATE_INTERVAL) {
    lastNtpUpdate = millis();
    ntpClient.update();
  }
  
  if (millis() - lastSerialTimeSend >= SERIAL_TIME_INTERVAL) {
    lastSerialTimeSend = millis();
    Serial.println(getFormattedTime());
  }
  
  if (millis() - lastWeatherUpdate >= WEATHER_UPDATE_INTERVAL) {
    lastWeatherUpdate = millis();
    fetchWeatherData();
  }
  checkClassEndTime();
}